package com.mt;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

//TODO redis连接池?
@Component
public class RedisUtils {
    @Autowired
    private RedisTemplate redisTemplate;

    //加锁失效时间，毫秒
    public static final int LOCK_EXPIRE = 300; // ms
    //锁名称
    public static final String LOCK_PREFIX = "redis_lock::";

    /**
     * 最终加强分布式锁
     *
     * @param key key值
     * @return 是否获取到
     */
    public boolean lock(String key) {
        String lock = LOCK_PREFIX + key;
        // 利用lambda表达式
        Boolean execute = (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
            Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
            if (acquire) {
                return true;
            } else {

                byte[] value = connection.get(lock.getBytes());

                if (Objects.nonNull(value) && value.length > 0) {

                    long expireTime = Long.parseLong(new String(value));
                    // 如果锁已经过期
                    if (expireTime < System.currentTimeMillis()) {
                        // 重新加锁，防止死锁
                        byte[] oldValue = connection.getSet(lock.getBytes(),
                                String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
                        return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                    }
                }
            }
            return false;
        });
        return execute;
    }

    /**
     * 分布式锁
     *
     * @param keys 同时set多个key
     * @return 是否获取到
     */

    public boolean lock(List<String> keys) {

        //String lock = LOCK_PREFIX + key;
        // 利用lambda表达式
        Boolean execute = (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            HashMap<byte[], byte[]> keyMap = new HashMap<>();
            long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
            for (String key : keys) {
                keyMap.put((LOCK_PREFIX + key).getBytes(), String.valueOf(expireAt).getBytes());
            }
            Boolean acquire = connection.mSetNX(keyMap);

            if (acquire) {
                return true;
            } else {
                 expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
                for (String key :keys){
                    String lock = LOCK_PREFIX + key;
                    byte[] value = connection.get(lock.getBytes());

                    if (Objects.nonNull(value) && value.length > 0) {
                        long expireTime = Long.parseLong(new String(value));
                        // 如果锁已经过期
                        if (expireTime < System.currentTimeMillis()) {
                            // 重新加锁，防止死锁
                            byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(expireAt).getBytes());
                            return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                        }
                    }
                }
            }
            return false;
        });
        return execute;
    }

    /**
     * 删除锁
     *
     * @param key
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }


    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 写入缓存并设置时效时间
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(String key, Object value, Long expireTime, TimeUnit timeUnit) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, timeUnit);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 读取缓存
     *
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * 判断缓存中是否有对应的value
     *
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    public boolean hexists(String hash, String key) {
        return redisTemplate.opsForHash().hasKey(hash, key);
    }

    /**
     * 删除对应的value
     *
     * @param key
     */
    public void remove(final String key) {

        try {
            if (exists(key)) {
                redisTemplate.delete(key);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }


    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    public Map<String, Object> getHashKeys(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
}
